题目描述:
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
示例 1:
1 | 输入:nums1 = [1,3], nums2 = [2] |
示例 2:
1 | 输入:nums1 = [1,2], nums2 = [3,4] |
示例 3:
1 | 输入:nums1 = [0,0], nums2 = [0,0] |
示例 4:
1 | 输入:nums1 = [], nums2 = [1] |
示例 5:
1 | 输入:nums1 = [2], nums2 = [] |
提示:
- $nums1.length == m$
- $nums2.length == n$
- $0 <= m <= 1000$
- $0 <= n <= 1000$
- $1 <= m + n <= 2000$
- $-10^6 <= nums1[i], nums2[i] <= 10^6$
链接:
https://leetcode-cn.com/problems/median-of-two-sorted-arrays
题目分析
1.合并数组解法
由于这是两个已经有序的数组,很容易想到归并排序。也就是将两个数组合并成一个,然后再返回处在中间的数字即可。注意合并的时候要考虑某一个数组已经遍历完毕的情况,另外对于奇数个数字和偶数个数字,中位数的计算也不同。
1 | class Solution { |
时间复杂度:$O(m+n)$,其中 $m、n$ 分别是两个数组的大小。因为顺序遍历了两个数组进行合并,合并后返回中位数的过程是 $O(1)$ 的。
空间复杂度:$O(m+n)$,其中 $m、n$ 分别是两个数组的大小。因为开辟了一个新数组用来存放合并后的数组。
2.合并数组解法改进
由于我们只需要寻找中位数,其实可以不用将真的将两个数组合并,只需要按顺序寻找到中位数就可以了。由于奇数和偶数计算中位数方式的不同,我们用变量 right
来记录当前第 cnt
小的数,而用 left
来记录上一个 right
也即第 cnt-1
小的数。遍历次数 cnt
为 $(m+n)/2+1$,这样当 $(m+n)$ 为奇数时,我们找到了第 $(m+n)/2+1$ 小的数也即中位数,返回 right
;当 $(m+n)$ 为偶数时,left
是第 $(m+n)/2$ 小的数,right
是第 $(m+n)/2+1$ 小的数,中位数是这两个数的平均值,返回 (left+right)/2.0
。(这也是变量命名的含义,中位数是左右两个数的平均值)
1 | class Solution { |
时间复杂度:$O(m+n)$,其中 $m、n$ 分别是两个数组的大小。思路和上面一致,但是只需要遍历一半,因为我们只需要找到中位数就可以停止了。
空间复杂度:$O(1)$。我们只需要常数个变量对遍历结果进行记录。
3.二分查找解法
由于数组都是有序的,而我们只需要找到处在中间的那个数,可以考虑用二分查找的方法加快搜索速度。一个思路是对于 nums1
、nums2
两个数组,我们要寻找第 k
小的数,则每个数组都寻找第 k/2
个数,假设分别为 nums1[k/2-1]
和 nums2[k/2-1]
,比较他们的大小。
- 若
nums1[k/2-1] >= nums2[k/2-1]
,则说明nums2[k/2-1]
最多也只能比nums1[0 ~ k/2-2]
和nums2[0 ~ k/2-2]
都大,最多只有 $(k/2-1)+(k/2-1)=k-2$ 个数比它小,则它小于第k
小的数,也即nums2[0 ~ k/2-1]
都可以进行排除。 - 若
nums1[k/2-1] < nums2[k/2-1]
,则相应排除nums1[0 ~ k/2-1]
。
在剩下的数组中,我们就要找第 k-(k/2)
小的数了。如此二分排除下去就能够得到答案,我们可以采用循环的写法,依然将 k-(k/2)
当做下一个 k
。这样的算法下,也有几种特殊情况。
- 数组中剩下的数字已经不够
k/2
个,这个时候,我们只需要选取数组最后一个数字作为比较对象,若进行排除则整个数组排除即可。当然,相应的,我们排除的数字个数可能就不再是k/2
,更新k
值的时候需要注意。 - 如果一个数组已经全部排除,则说明中位数存在于另外一个数组中,我们直接返回另外一个数组中第
k
个数即可。 - 若
k
为1
,则到了循环的终点,我们返回两个数组中首元素值更小的那个数即可。
1 | class Solution { |
时间复杂度:$O(log(m+n))$,其中 $m、n$ 分别是两个数组的大小。因为初始 $k=(m+n)/2$ 或 $k=(m+n)/2+1$,而每一次搜索会将范围减半。
空间复杂度:$O(1)$。我们只需要常数个变量对搜索结果进行记录。
4.划分数组解法
从另一个角度想,中位数其实就是将数组分割为两个相等长度的子集。而数组的长度我们是知道的,则每个子集的长度我们也是知道的。则我们在 i
处将 nums1
分割为两部分时,相应也就知道了在 nums2
中的哪一处分割为两部分,假设为 j
。如果满足 nums1[i-1] <= nums2[j]
且 nums1[i] >= nums2[j-1]
。即左半部分为 nums1[0 ~ i-1]
和 nums2[0 ~ j-1]
,右半部分为 nums1[i ~ m-1]
和 nums2[j ~ n-1]
。此时可以满足左边的所有值都小于右边,则中位数就找到了。这样我们只需要对其中某一个数组进行二分查找,时间复杂度会更低一些。
- 若
m+n
为偶数,则我们应该有 $i+j=m-i+n-j$。
中位数是(max(nums1[i-1], nums2[j-1])+min(nums1[i], nums2[j]))/2
。 - 若
m+n
为奇数,则我们应该有 $i+j=m-i+n-j+1$。(左半部分多一个数)
中位数是max(nums1[i-1], nums2[j-1])
。 - 根据上面两点可以得到 $j = \lfloor\frac{m+n+1}{2}\rfloor-i$。
我们令nums1
的长度小于nums2
的长度,则可以保证 $j\in[0,n]$。 - 如果
nums1
长度大于nums2
则交换两个数组即可。 - 对于整个数组都存在某半部分的情况,我们可以令
nums1[-1]=nums2[-1]=INT_MIN
,nums1[m]=nums2[n]=INT_MAX
。这样不会对左半部分的最大值或者右半部分的最小值产生影响。
PS::代码来自官方题解。
1 | class Solution { |
时间复杂度:$O(log(min(m,n)))$,其中 $m、n$ 分别是两个数组的大小。这是因为我们只对长度较小的那个数组进行了二分查找。
空间复杂度:$O(1)$。我们只需要常数个变量对查找结果进行记录。